﻿using System;
using System.Data;
using System.Data.Entity.Design.PluralizationServices;
using System.Data.Objects.DataClasses;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;

namespace Citi.ActionItem.Domain.Utils
{
    public class GeneralAction : EntityObject
    {
        #region Public Properties

        private static EntityObject Entity { get; set; }
        private bool IsPluralized { get; set; }
        private const string Active = "Active";
        private const string Id = "Id";

        #endregion

        #region Constructor
        /// <summary>
        /// General methods for Entity Framework. Default parent class entity and pluralization true.
        /// </summary>
        public GeneralAction()
        {
            Entity = this;
            IsPluralized = true;
        }

        /// <summary>
        /// General methods for Entity Framework. Default pluralization true.
        /// </summary>
        /// <param name="entity">Local entity</param>
        public GeneralAction(EntityObject entity)
        {
            Entity = entity;
            IsPluralized = true;
        }

        /// <summary>
        /// General methods for Entity Framework.
        /// </summary>
        /// <param name="entity">Local entity</param>
        /// <param name="isPluralized">Model is pluralized</param>
        public GeneralAction(EntityObject entity, bool isPluralized)
        {
            Entity = entity;
            IsPluralized = isPluralized;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Saves object in database. 
        /// If EntityState is Added or Detached will be add on database.
        /// If EntityState is Unchanged will return and not accessing database
        /// If EntityState is Deleted will be delete on database
        /// If EntityState is Modified will be change on database
        /// </summary>
        public void Save()
        {
            //Context.ClearContext();

            switch (GetEntityState())
            {
                case EntityState.Added:
                case EntityState.Detached:
                    Context.Instance.AddObject(GetEntityName(Entity.GetType()), Entity);
                    break;
                case EntityState.Unchanged:
                    return;
                case EntityState.Deleted:
                    Delete();
                    return;
            }

            try
            {
                Context.SaveChanges();
            }
            catch (UpdateException)
            {
                Context.Instance.Detach(Entity);
                throw;
            }
        }

        public void Save<T>(T entity) where T : EntityObject
        {
            var entityName = IsPluralized
                    ? PluralizationService.CreateService(new CultureInfo("EN-US")).Pluralize(entity.GetType().Name)
                    : entity.GetType().Name;

            PropertyInfo propertyInfo = entity.GetType().GetProperty("EntityState");
            var entityState = (EntityState)propertyInfo.GetValue(entity, null);




            try
            {
                switch (entityState)
                {
                    case EntityState.Added:
                    case EntityState.Detached:
                        Context.Instance.AddObject(entityName, entity);
                        break;
                    case EntityState.Unchanged:
                        return;
                    case EntityState.Deleted:
                        Delete();
                        return;
                }

                Context.SaveChanges();
            }
            catch (InvalidOperationException)
            {
                //Context.Instance.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
                //Context.Instance.CreateObjectSet<T>().ApplyCurrentValues(entity);
                Context.Instance.Attach(entity);
                Context.SaveChanges();
            }
            catch (UpdateException)
            {
                Context.Instance.Detach(entity);
                throw;
            }
        }

        public void Detach()
        {
            //   Context.Instance.DeleteObject(this);
        }

        /// <summary>
        /// Deletes object 
        /// </summary>
        public void Delete()
        {
            if (GetEntityState() == EntityState.Detached)
                throw new EntityException("Entity is not attached");

            Context.Delete(Entity);
        }

        /// <summary>
        /// Deletes object 
        /// </summary>
        public void Delete(EntityObject entity)
        {
            if (entity.EntityState == EntityState.Detached)
                throw new EntityException("Entity is not attached");

            Context.Delete(entity);
        }

        /// <summary>
        /// Deletes object 
        /// </summary>
        public void Delete<T>(int id) where T : class
        {
            var entity = GetContextInstance<T>().FirstOrDefault(e => GetProperty<int>(e, Id) == id);

            if (entity == null) return;

            Context.Delete(entity);
        }

        /// <summary>
        /// Inatives object 
        /// </summary>
        public void Inative()
        {
            if (GetEntityState() == EntityState.Detached)
                throw new EntityException("Entity is not attached");

            if (!ExistsProperty(Entity.GetType(), Active))
            {
                Delete();
                return;
            }

            Entity.GetType().GetProperty(Active).SetValue(Entity, false, null);
            Save();
        }

        public void Inative(EntityObject entity)
        {
            if (entity.EntityState == EntityState.Detached)
                throw new EntityException("Entity is not attached");

            if (entity.GetType().GetProperty(Active) == null)
            {
                Delete(entity);
                return;
            }

            entity.GetType().GetProperty(Active).SetValue(entity, false, null);
            Save(entity);
        }

        /// <summary>
        /// Select from database by Id. If the entity doesn't have the Id property will occur an Exception
        /// </summary>
        /// <typeparam name="T">Entity type</typeparam>
        /// <param name="id">Id</param>
        /// <returns>Entity by Id</returns>
        public T SelectById<T>(int id) where T : new()
        {
            if (!ExistsProperty(typeof(T), Id))
                throw new Exception("Do not exist the property");

            return
                (from T entity in GetContextInstance<T>()
                 where GetProperty<int>(entity, Id) == id
                 select entity).FirstOrDefault();
        }

        /// <summary>
        /// Select from database by Id. If the entity doesn't have the Id property will occur an Exception
        /// </summary>
        /// <typeparam name="T">Entity type</typeparam>
        /// <param name="id">Id</param>
        /// <returns>Entity active by Id</returns>
        public T SelectActiveById<T>(int id) where T : new()
        {
            if (!ExistsProperty(typeof(T), Active) || !ExistsProperty(typeof(T), Id))
                throw new Exception("Do not exist the property");

            return
                (from T entity in GetContextInstance<T>()
                 where GetProperty(entity, Id).Equals(id)
                       && GetProperty(entity, Active).Equals(true)
                 select entity).FirstOrDefault();
        }

        /// <summary>
        /// Select all from database
        /// </summary>
        /// <typeparam name="T">Entity type</typeparam>
        /// <returns>All entities</returns>
        public List<T> SelectAll<T>()
        {
            return (from T entity in GetContextInstance<T>() select entity).ToList();
        }

        /// <summary>
        /// Select all active from database
        /// </summary>
        /// <typeparam name="T">Entity type</typeparam>
        /// <returns>All Actives entities</returns>
        public List<T> SelectAllActive<T>() where T : new()
        {
            if (ExistsProperty(typeof(T), Active))
                return (from T entity in GetContextInstance<T>()
                        where GetProperty(entity, Active).Equals(true)
                        select entity).ToList();

            return SelectAll<T>();
        }

        #endregion

        #region Private Methods

        private object GetProperty(object entity, string propertyName)
        {
            return entity.GetType().GetProperty(propertyName).GetValue(entity, null);
        }

        private T GetProperty<T>(object entity, string propertyName)
        {
            // var c = new Converter<object, T>(input => (T)Convert.ChangeType(input, typeof(T)));
            // return new Converter(). <object, T>(entity.GetType().GetProperty(propertyName).GetValue(entity, null), c));

            //return new Converter<int, T>(input => (T)Convert.ChangeType(input, typeof(T)));

            //return entity.GetType().GetProperty(propertyName).GetValue(entity, null);
            return (T)Convert.ChangeType(entity.GetType().GetProperty(propertyName).GetValue(entity, null), typeof(T));
        }

        /// <summary>
        /// Get EF instance
        /// </summary>
        /// <typeparam name="T">Entity type</typeparam>
        /// <returns>Database Container</returns>
        private List<T> GetContextInstance<T>()
        {
            return ((IEnumerable<T>)Context.Instance.GetType().GetProperty(GetEntityName(typeof(T))).GetValue(Context.Instance, null)).ToList();
        }

        /// <summary>
        /// Verify if exists a property in entity
        /// </summary>
        /// <param name="property">Property name</param>
        private static bool ExistsProperty(Type entity, string property)
        {
            return entity.GetProperty(property) != null;
            //throw new EntityException(string.Concat("The entity ", new T().GetType().Name, " does not have the property ", property));
        }

        /// <summary>
        /// Gets entity state from local entity
        /// </summary>
        /// <returns>Local entity state</returns>
        private EntityState GetEntityState()
        {
            PropertyInfo propertyInfo = Entity.GetType().GetProperty("EntityState");
            return (EntityState)propertyInfo.GetValue(Entity, null);
        }

        /// <summary>
        /// Gets entity name. Pluralized if is need.
        /// </summary>
        /// <returns>Entity name</returns>
        private string GetEntityName(Type entityType)
        {
            return IsPluralized
                ? PluralizationService.CreateService(new CultureInfo("EN-US")).Pluralize(entityType.Name)
                : entityType.Name;
        }

        //private string GetEntityName()
        //{
        //    return IsPluralized
        //        ? PluralizationService.CreateService(new CultureInfo("EN-US")).Pluralize(Entity.GetType().Name)
        //        : Entity.GetType().Name;
        //}

        public object SelectJoin<T>()
        {
            var test = (from teste in GetContextInstance<T>()
                        join teste2 in GetContextInstance<T>() on GetProperty(teste, "id") equals
                            GetProperty(teste2, "idTeste")
                        select new { });

            return null;
        }

        public object Join<T>(IEnumerable<T> entityBase)
        {
            //var results = Enumerable.Join(entityBase,
            //            teste => GetProperty(teste, "id"),
            //            other => GetProperty(other, "id"),
            //            (teste, other) => new
            //            {
            //                MissingField = other.Field,
            //                OtherMissingField = other.OtherField
            //            });
            return from s in GetContextInstance<T>()
                   join h in GetContextInstance<T>() on Teste(s) equals Teste2(h)
                   select s;
        }

        private object Teste(object linq)
        {
            return new { GetProperty = GetProperty(linq, "id") };
        }
        private object Teste2(object linq)
        {
            return new { GetProperty = GetProperty(linq, "id") };
        }

        #endregion
    }
}
